﻿using System;
using System.Collections.Generic;
using System.Text;

namespace Arcana.Contract.Property
{
    /// <summary>
    /// The base class for all contracts that validate properties.
    /// </summary>
    /// <remarks>
    /// <b>Note to Implementers: </b> Implementing <see cref="ContractBase.ValidateProperty"/> is important. A <b>true</b> or
    /// <b>false</b> value will indicate the validation status. <see cref="ContractBase"/> class is responsible for throwing
    /// exceptions, if necessary.
    /// </remarks>
    [Serializable]
    public abstract class ContractBase : PostSharp.Aspects.LocationInterceptionAspect, IContract
    {
        private Type _exception;

        /// <summary>
        /// Initializes a new instance of <b>ContractBase</b>.
        /// </summary>
        /// <remarks>
        /// <b>Note to Implementers: </b> Implementing <see cref="ContractBase.ValidateProperty"/> is important. A <b>true</b> or
        /// <b>false</b> value will indicate the validation status. <see cref="ContractBase"/> class is responsible for throwing
        /// exceptions, if necessary.
        /// </remarks>
        public ContractBase()
        {
            _exception = typeof(System.ArgumentException);
        }

        /// <summary>
        /// Validates the property <i>value</i> during get/set scenarios.
        /// </summary>
        /// <remarks>
        /// This method is called internally to validate a property. this method must return <b>true</b> if property
        /// <i>value</i> is valid, otherwise it must return <b>false</b>. The <see cref="ContractBase"/> is responsible
        /// to generate exceptions itsef.
        /// </remarks>
        /// <param name="value">The property value during get/set scenarios.</param>
        /// <returns><b>true</b> if <i>value</i> is valid. otherwise <i>false</i>.</returns>
        protected abstract bool ValidateProperty(object value);

        /// <summary>
        /// Method invoked instead of the Get semantic of the field or property to which the current aspect is applied, i.e. When the value of this field or property is going to be retrieved.
        /// </summary>
        /// <remarks>
        /// <b>Note to Implementers: </b> Implementing <see cref="ContractBase.ValidateProperty"/> is important. A <b>true</b> or
        /// <b>false</b> value will indicate the validation status. <see cref="ContractBase"/> class is responsible for throwing
        /// exceptions, if necessary.
        /// </remarks>
        /// <param name="args">Advice arguments.</param>
        public sealed override void OnGetValue(PostSharp.Aspects.LocationInterceptionArgs args)
        {
            args.ProceedGetValue();
            if (!ValidateProperty(args.Value))
            {
                ThrowException();
            }
        }

        /// <summary>
        /// Method invoked instead of the Set semantic of the field or property to which the current aspect is applied, i.e. When the value of this field or property is going to be changed.
        /// </summary>
        /// <remarks>
        /// <b>Note to Implementers: </b> Implementing <see cref="ContractBase.ValidateProperty"/> is important. A <b>true</b> or
        /// <b>false</b> value will indicate the validation status. <see cref="ContractBase"/> class is responsible for throwing
        /// exceptions, if necessary.
        /// </remarks>
        /// <param name="args">Advice arguments.</param>
        public sealed override void OnSetValue(PostSharp.Aspects.LocationInterceptionArgs args)
        {
            if (!ValidateProperty(args.Value))
            {
                ThrowException();
            }
            args.ProceedSetValue();
        }

        /// <summary>
        /// Throws exception appointed by <see cref="ContractBase.Exception"/>
        /// </summary>
        /// <remarks>
        /// <b>Note to Implementers: </b> Implementing <see cref="ContractBase.ValidateProperty"/> is important. A <b>true</b> or
        /// <b>false</b> value will indicate the validation status. <see cref="ContractBase"/> class is responsible for throwing
        /// exceptions, if necessary.
        /// </remarks>
        protected virtual void ThrowException()
        {
            throw Exception.GetConstructor(new Type[] { }).Invoke(new object[] { }) as System.Exception;
        }

        /// <summary>
        /// Represents type of exception to throw if contract validation fails.
        /// </summary>
        /// <value>
        /// The type of exception to throw, if the contract validation fails.
        /// </value>
        /// <remarks>
        /// This value must inherit from <see cref="System.Exception"/> class. This value is set to
        /// <see cref="System.ArgumentException"/> by default. <br />
        /// <b>Note to Implementers: </b> Implementing <see cref="ContractBase.ValidateProperty"/> is important. A <b>true</b> or
        /// <b>false</b> value will indicate the validation status. <see cref="ContractBase"/> class is responsible for throwing
        /// exceptions, if necessary.
        /// </remarks>
        /// <exception cref="ArgumentNullException"><i>value</i> is <b>null</b>.</exception>
        /// <exception cref="ArgumentException"><i>value</i> does not inherit from <see cref="System.Exception"/></exception>
        public Type Exception
        {
            get
            {
                return _exception;
            }
            set
            {
                if (value == null) throw new ArgumentNullException();
                if (!value.IsSubclassOf(typeof(System.Exception))) throw new ArgumentException();
                _exception = value;
            }
        }
    }
}
